/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *
 *              C E D A R
 *          S O L U T I O N S       "Software done right."
 *           S O F T W A R E
 *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *
 * Copyright (c) 2011 Kenneth J. Pronovici.
 * All rights reserved.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the Apache License, Version 2.0.
 * See doc/LICENSE for more information about the licensing terms.
 *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *
 * Author   : Kenneth J. Pronovici <pronovic@ieee.org>
 * Language : Java 6
 * Project  : Secret Santa Exchange
 *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
package com.cedarsolutions.santa.client.common.presenter;

import com.cedarsolutions.santa.client.SantaExchangeEventTypes;
import com.cedarsolutions.santa.client.common.widget.WidgetUtils;
import com.cedarsolutions.santa.client.rpc.IClientSessionRpcAsync;
import com.cedarsolutions.santa.client.rpc.util.AsyncRpcCallback;
import com.cedarsolutions.santa.shared.domain.ClientSession;
import com.cedarsolutions.santa.shared.domain.Module;
import com.google.inject.Inject;
import com.mvp4g.client.event.BaseEventHandler;
import com.mvp4g.client.event.EventBusWithLookup;


/**
 * Client session event handler for a module.
 *
 * <p>
 * This class handles events to initialize and clear the session in a standard
 * way.  Since each module gets its own instance of the client session singleton,
 * each module must create its own client session event handler, inheriting from
 * this class.
 * </p>
 *
 * <p>
 * One module in the application (probably the root module) should own the
 * server-side session.  This module's session event handler should override
 * <code>moduleOwnsServerSideSession()</code> to return true.  This causes
 * the code below to make an RPC call to invalidate the server-side session.
 * </p>
 *
 * <p>
 * It would be nice to pull this out of the application up into CedarCommon.
 * However, the implementation is just too tightly coupled to the application
 * (i.e. type of the client session, calls to WidgetUtils to get URLs and
 * display "please wait messages", etc.).  It would be possible to make a generic
 * class, but it wouldn't be that readable.  Better to just copy it into other
 * applications later.
 * </p>
 *
 * @param <E> Type of the event bus used by the event handler
 *
 * @author Kenneth J. Pronovici <pronovic@ieee.org>
 */
public abstract class ModuleClientSessionEventHandler<E extends EventBusWithLookup> extends BaseEventHandler<E> {

    /** Client session used by this module. */
    @Inject private ClientSession session;

    /** Client session service. */
    @Inject private IClientSessionRpcAsync clientSessionRpc;

    /** Get the current module. */
    public abstract Module getModule();

    /**
     * Indicates whether this module owns the server-side session.
     * Only one module should own the session, probably the root module.
     */
    public boolean moduleOwnsServerSideSession() {
        return false;
    }

    /** Initialize the current session.*/
    public void onInitializeSession(String eventType, Object[] params) {
        WidgetUtils.getInstance().showPleaseWaitProgressIndicator();
        String logoutDestinationUrl = WidgetUtils.getInstance().getDestinationUrl(SantaExchangeEventTypes.LANDING_PAGE);
        EstablishClientSessionCallback callback = new EstablishClientSessionCallback(this.getModule(),
                                                                                     this.session, this.clientSessionRpc,
                                                                                     this.eventBus, eventType, params);
        clientSessionRpc.establishClientSession(this.getModule(), logoutDestinationUrl, callback);
    }

    /** Clear the current session. */
    public void onClearSession() {
        if (this.moduleOwnsServerSideSession()) {
            // If this module owns the server-side session, then call down and clear it at the server.
            WidgetUtils.getInstance().showPleaseWaitProgressIndicator();
            InvalidateClientSessionCallback callback = new InvalidateClientSessionCallback(this.session);
            clientSessionRpc.invalidateClientSession(this.getModule(), callback);
        } else {
            // Otherwise, just clear our session reference and someone else deal with the server side.
            this.session.clear();
        }
    }

    public ClientSession getSession() {
        return this.session;
    }

    public void setSession(ClientSession session) {
        this.session = session;
    }

    public IClientSessionRpcAsync getClientSessionRpc() {
        return this.clientSessionRpc;
    }

    public void setClientSessionRpc(IClientSessionRpcAsync clientSessionRpc) {
        this.clientSessionRpc = clientSessionRpc;
    }

    /** Callback for IClientSessionRpc.establishClientSession(). */
    protected static class EstablishClientSessionCallback extends AsyncRpcCallback<ClientSession> {

        protected Module module;
        protected ClientSession session;
        protected IClientSessionRpcAsync service;
        protected EventBusWithLookup eventBus;
        protected String eventType;
        protected Object[] params;
        protected boolean failedOnceAlready;

        public EstablishClientSessionCallback(Module module,
                                              ClientSession session,
                                              IClientSessionRpcAsync service,
                                              EventBusWithLookup eventBus,
                                              String eventType, Object[] params) {
            super("IClientSessionRpc", "establishClientSession");
            this.module = module;
            this.session = session;
            this.service = service;
            this.eventBus = eventBus;
            this.eventType = eventType;
            this.params = params;
            this.failedOnceAlready = false;
        }

        @Override
        public void onSuccessResult(ClientSession result) {
            this.session.refresh(result);
            this.dispatchEvent();
        }

        @Override
        public void onUnhandledError(Throwable caught) {
            if (!this.failedOnceAlready) {
                // It appears to (sometimes?) fail the first time after we clear the session.
                // So, the first time it fails, we'll automatically retry it.
                this.failedOnceAlready = true;
                WidgetUtils.getInstance().showPleaseWaitProgressIndicator();
                String logoutDestinationUrl = WidgetUtils.getInstance().getDestinationUrl(SantaExchangeEventTypes.LANDING_PAGE);
                this.service.establishClientSession(module, logoutDestinationUrl, this);
            } else {
                // If it fails more than once, that's a permanent failure that we should report.
                super.onUnhandledError(caught);
            }
        }

        private void dispatchEvent() {
            if (this.eventType != null && this.params != null) {
                this.eventBus.dispatch(this.eventType, this.params);
            } else if (this.eventType != null) {
                this.eventBus.dispatch(this.eventType);
            }
        }
    }

    /** Callback for IClientSessionRpc.invalidateClientSession(). */
    protected static class InvalidateClientSessionCallback extends AsyncRpcCallback<Void> {

        protected ClientSession session;

        public InvalidateClientSessionCallback(ClientSession session) {
            super("IClientSessionRpc", "invalidateClientSession");
            this.session = session;
        }

        @Override
        public void onSuccessResult(Void result) {
            this.session.clear();
        }

        @Override
        public void onUnhandledError(Throwable caught) {
            // This is expected to fail, because once we invalidate the
            // session, it's not possible to return a response to the client.
            // That's kind of ugly, but I can't figure out how to work around
            // it.  So, we're just going to ignore all failures.  There's not
            // anything we can do if it fails.
            this.session.clear();
        }
    }
}
